package mq

import (
	log "cyiris/core/zaplog"
	"time"

	"github.com/streadway/amqp"
)

type Channel struct {
	channel         *amqp.Channel
	notifyChanClose chan *amqp.Error
	notifyConfirm   chan amqp.Confirmation
	isReady         bool
	done            chan bool
}

const (
	// When setting up the channel after a channel exception
	reInitDelay = 2 * time.Second
	resendDelay = 5 * time.Second
)

func NewChannel(conn *Connector) *Channel {
	chann := &Channel{
		isReady: false,
		done:    make(chan bool),
	}

	go chann.handleReInit(conn)

	// 确保通道创建成果才返回
	for {
		if !chann.isReady {
			log.Debugf("Channel is not ready, waiting...")
			select {
			case <-chann.done:
				return nil
			case <-time.After(time.Millisecond * 20):
				continue
			}
		}

		break
	}

	return chann
}

func (chann *Channel) handleReInit(conn *Connector) bool {
	for {
		if !conn.isReady {
			log.Debugf("Connector is not ready, waiting...")
			select {
			case <-chann.done:
				return true
			case <-time.After(time.Millisecond * 20):
			}
			continue
		}

		chann.isReady = false

		err := chann.init(conn.connection)

		if err != nil {
			log.Warnf("Failed to initialize channel. Retrying...")

			select {
			case <-chann.done:
				return true
			case <-time.After(reInitDelay):
			}
			continue
		}

		select {
		case <-chann.done:
			return true
		case <-chann.notifyChanClose:
			log.Warnf("Channel closed. Re-running init...")
		}
	}
}

// init will initialize channel & declare queue
func (chann *Channel) init(conn *amqp.Connection) error {
	ch, err := conn.Channel()

	if err != nil {
		return err
	}

	err = ch.Confirm(false)

	if err != nil {
		return err
	}

	chann.changeChannel(ch)
	chann.isReady = true
	log.Infof("Channel Setup!")

	return nil
}

// changeChannel takes a new channel to the queue,
// and updates the channel listeners to reflect this.
func (chann *Channel) changeChannel(channel *amqp.Channel) {
	chann.channel = channel
	chann.notifyChanClose = make(chan *amqp.Error)
	chann.notifyConfirm = make(chan amqp.Confirmation, 1)
	chann.channel.NotifyClose(chann.notifyChanClose)
	chann.channel.NotifyPublish(chann.notifyConfirm)
}
